<?php

namespace app\api\controller;

use ba\Random;
use Throwable;
use ba\Captcha;
use think\facade\Db;
use think\response\Redirect;
use app\admin\model\oauth\Log;
use app\common\controller\Frontend;
use app\api\validate\Account as AccountValidate;

class OAuthLogin extends Frontend
{
    protected array $noNeedLogin = ['index', 'loginAgent', 'oauthList', 'wechatMiniProgram'];

    protected array $noNeedPermission = ['bindList', 'unbind', 'changePassword'];

    /**
     * @var Captcha
     */
    protected Captcha $captcha;

    public function initialize(): void
    {
        parent::initialize();
        $this->captcha = new Captcha();
    }

    /**
     * 三方鉴权模块单独提供一个修改密码的接口
     * 可以在用户通过第三方登录后，无需填写旧密码完成密码的首次设定
     * @throws Throwable
     */
    public function changePassword(): void
    {
        $allowSetPassword = $this->allowSetPassword();

        if ($this->request->isPost()) {
            $params = $this->request->only(['oldPassword', 'newPassword']);

            if (!$allowSetPassword && (!isset($params['oldPassword']) || !$this->auth->checkPassword($params['oldPassword']))) {
                $this->error(__('Old password error'));
            }

            Db::startTrans();
            try {
                $validate = new AccountValidate();
                $validate->scene('changePassword')->check(['password' => $params['newPassword']]);
                $this->auth->getUser()->resetPassword($this->auth->id, $params['newPassword']);
                Db::commit();
            } catch (Throwable $e) {
                Db::rollback();
                $this->error($e->getMessage());
            }

            if (!$allowSetPassword) $this->auth->logout();
            $this->success($allowSetPassword ? __('The password is set successfully, and the page will refresh automatically~') : __('Password has been changed, please login again~'), [
                'allowSetPassword' => $allowSetPassword
            ]);
        }
        $this->success('', [
            'allowSetPassword' => $allowSetPassword
        ]);
    }

    /**
     * 账号绑定/解绑的第三方平台列表
     */
    public function bindList(): void
    {
        $sources     = [];
        $config      = config('oauth');
        $bindSources = Db::name('oauth_log')
            ->where('user_id', $this->auth->id)
            ->column('source');
        foreach ($config as $name => $item) {
            if (self::checkState($item)) {
                $sources[$name] = in_array($name, $bindSources) ? 'Bound' : 'Unbound';
            }
        }

        $this->success('', [
            'sources' => $sources,
        ]);
    }

    /**
     * 可用的三方授权登录平台列表
     */
    public function oauthList(): void
    {
        $oauthNames = [];
        $config     = config('oauth');
        foreach ($config as $name => $item) {
            if (self::checkState($item)) {
                $oauthNames[] = $name;
            }
        }
        $this->success('', [
            'oauthNames' => $oauthNames,
        ]);
    }

    /**
     * 解除绑定
     */
    public function unbind(): void
    {
        $name     = $this->request->post('name');
        $password = $this->request->post('password');
        $config   = config('oauth');
        if (!isset($config[$name]) || !self::checkState($config[$name])) {
            $this->error(__('Unlocked authorized login mode'));
        }

        if (!isset($password) || !verify_password($password, $this->auth->password, ['salt' => $this->auth->salt])) {
            $this->error(__('Password error'));
        }

        Db::name('oauth_log')
            ->where('user_id', $this->auth->id)
            ->where('source', $name)
            ->delete();
        $this->success(__('Unbinding succeeded!'), [
            'name' => $name
        ]);
    }

    /**
     * 生成授权登录 URL 并自动跳转
     * @return void | Redirect
     * @throws Throwable
     */
    public function index()
    {
        $type  = $this->request->get('type');
        $name  = $this->request->get('name');
        $token = $this->request->get('token', '');

        if (!$type) {
            $this->error(__('Parameter error'));
        }

        $config = config('oauth');
        if (!isset($config[$name]) || !self::checkState($config[$name])) {
            $this->error(__('Unlocked authorized login mode'));
        }

        $config  = $config[$name];
        $state   = Random::uuid();
        $captcha = $this->captcha->create($state);
        $state   = "{$name}__{$state}__{$captcha}__{$type}__$token";
        $url     = '';
        if ($name == 'qq') {
            $OAuth = new \Yurun\OAuthLogin\QQ\OAuth2($config['app_id'], $config['app_secret'], $config['callback_url']);
            $url   = $OAuth->getAuthUrl($config['callback_url'], $state);
        } elseif ($name == 'wechat_scan' || $name == 'wechat_mp') {
            $OAuth = new \Yurun\OAuthLogin\Weixin\OAuth2($config['app_id'], $config['app_secret'], $config['callback_url']);
            $url   = $name == 'wechat_mp' ? $OAuth->getWeixinAuthUrl($config['callback_url'], $state) : $OAuth->getAuthUrl($config['callback_url'], $state);
        }
        if ($url) {
            return redirect($url);
        } else {
            $this->error(__('Failed to generate authorization URL'));
        }
    }

    /**
     * 同意授权登录（qq、微信扫码、微信公众号）
     * @throws Throwable
     */
    public function loginAgent(): void
    {
        $state          = $this->request->get('state/s', '');
        $availableState = null;
        $bindUserToken  = null;
        if ($state) {
            $stateArr      = explode('__', $state);
            $name          = $stateArr[0] ?? false;
            $type          = $stateArr[3] ?? false;
            $bindUserToken = $stateArr[4] ?? null;

            if ($this->captcha->check($stateArr[2] ?? '', $stateArr[1] ?? '')) {
                $availableState = $state;
            }
        }

        if (empty($availableState) || empty($name) || empty($type)) {
            $this->error(__('Parameter error'));
        }

        $config = config('oauth');
        if (!isset($config[$name]) || !self::checkState($config[$name])) {
            $this->error(__('Unlocked authorized login mode'));
        }

        $config = $config[$name];
        if ($name == 'qq') {
            $OAuth = new \Yurun\OAuthLogin\QQ\OAuth2($config['app_id'], $config['app_secret'], $config['callback_url']);
        } elseif ($name == 'wechat_scan' || $name == 'wechat_mp') {
            $OAuth             = new \Yurun\OAuthLogin\Weixin\OAuth2($config['app_id'], $config['app_secret'], $config['callback_url']);
            $OAuth->openidMode = \Yurun\OAuthLogin\Weixin\OpenidMode::UNION_ID_FIRST;
        } else {
            $this->error(__('Unsupported authorized login mode'));
        }

        $res = false;
        Db::startTrans();
        try {
            $OAuth->getAccessToken($availableState);
            $userInfo  = $OAuth->getUserInfo();
            $openid    = $OAuth->openid;
            $oauthData = Log::where('uuid', $openid)->where(function ($query) use ($name) {
                if ($name == 'wechat_scan' || $name == 'wechat_mp') {
                    $query->where('source', 'in', ['wechat_scan', 'wechat_mp']);
                } else {
                    $query->where('source', $name);
                }
            })->find();

            if ($type == 'login') {
                $avatar = self::httpToHttps($name == 'qq' ? ($userInfo['figureurl_qq_2'] ?? '') : ($userInfo['headimgurl'] ?? ''));

                // 登录
                if ($oauthData) {
                    $res = $this->auth->direct($oauthData->user_id);
                    if ($res) {
                        $user = $this->auth->getUser();

                        // 更新用户头像
                        if (stripos($user->avatar, 'qlogo.cn') !== false && $user->avatar != $avatar) {
                            $user->avatar = $avatar;
                            $user->save();
                        }
                    }
                }

                // 创建用户
                if (!$oauthData && is_array($userInfo)) {
                    $res = $this->auth->register(self::generateUserName($name), '', '', '', 1, [
                        'avatar'   => $avatar,
                        'nickname' => $userInfo['nickname'],
                        'password' => ''
                    ]);

                    if ($res) {
                        Log::create([
                            'user_id' => $this->auth->id,
                            'source'  => $name,
                            'uuid'    => $openid,
                            'extend'  => json_encode($userInfo, JSON_UNESCAPED_UNICODE),
                        ]);
                    }
                }
            }

            if ($type == 'bind') {

                if ($bindUserToken) {
                    $this->auth->init($bindUserToken);
                }

                if ($this->auth->isLogin()) {
                    Log::create([
                        'user_id' => $this->auth->id,
                        'source'  => $name,
                        'uuid'    => $openid,
                        'extend'  => json_encode($userInfo, JSON_UNESCAPED_UNICODE),
                    ]);
                    $res = true;
                }
            }

            Db::commit();
        } catch (Throwable $e) {
            Db::rollback();
            $this->error($e->getMessage());
        }

        if (!$res) {
            $this->error(__('Login authorization failed, please try again'));
        }

        $this->success(__('Login succeeded!'), [
            'userInfo' => $this->auth->getUserInfo(),
            'type'     => $type
        ]);
    }

    /**
     * 微信小程序登录（auth.code2Session 接口，code 换 OpenID、UnionID、session_key）
     * @throws Throwable
     */
    public function wechatMiniProgram(): void
    {
        $name = 'wechat_mini_program';
        $type = $this->request->param('type', 'login');
        $code = $this->request->param('code');

        if (!$code) {
            $this->error(__('Parameter error'));
        }

        $config = config('oauth');
        if (!isset($config[$name]) || !self::checkState($config[$name])) {
            $this->error(__('Unlocked authorized login mode'));
        }

        $config            = $config[$name];
        $OAuth             = new \Yurun\OAuthLogin\Weixin\OAuth2($config['app_id'], $config['app_secret']);
        $OAuth->openidMode = \Yurun\OAuthLogin\Weixin\OpenidMode::UNION_ID_FIRST;
        $OAuth->getSessionKey($code);

        $res = false;
        Db::startTrans();
        try {
            $openid    = $OAuth->openid;
            $oauthData = Log::where('uuid', $openid)->where('source', $name)->find();

            if ($type == 'login') {
                // 直接登录
                if ($oauthData) {
                    $res = $this->auth->direct($oauthData->user_id);
                }

                // 创建新用户
                if (!$oauthData) {
                    $username = self::generateUserName($name);
                    $res      = $this->auth->register($username, '', '', '', 1, [
                        'avatar'   => '',
                        'nickname' => $username,
                        'password' => ''
                    ]);

                    if ($res) {
                        Log::create([
                            'user_id' => $this->auth->id,
                            'source'  => $name,
                            'uuid'    => $openid,
                            'extend'  => json_encode($OAuth->result, JSON_UNESCAPED_UNICODE),
                        ]);
                    }
                }
            }

            if ($type == 'bind') {
                $bindUserToken = $this->request->param('bindUserToken');
                if ($bindUserToken) {
                    $this->auth->init($bindUserToken);
                }
                if ($this->auth->isLogin()) {
                    Log::create([
                        'user_id' => $this->auth->id,
                        'source'  => $name,
                        'uuid'    => $openid,
                        'extend'  => json_encode($OAuth->result, JSON_UNESCAPED_UNICODE),
                    ]);
                    $res = true;
                }
            }

            Db::commit();
        } catch (Throwable $e) {
            Db::rollback();
            $this->error($e->getMessage());
        }

        if (!$res) {
            $this->error(__('Login authorization failed, please try again'));
        }

        $this->success(__('Login succeeded!'), [
            'userInfo' => $this->auth->getUserInfo(),
            'type'     => $type
        ]);
    }

    /**
     * 是否允许设定密码（无需输入旧密码）
     * 1、无第三方鉴权记录，不允许设定密码
     * 2、已经设定过密码记录，不允许设定密码
     * @throws Throwable
     */
    private function allowSetPassword(): bool
    {
        $log = Log::where('user_id', $this->auth->id)->select();
        if ($log->isEmpty()) return false;
        if ($this->auth->password) return false;
        return true;
    }

    private static function checkState($arr): bool
    {
        if (!is_array($arr)) return false;
        foreach ($arr as $item) {
            if (!$item) return false;
        }
        return true;
    }

    private static function generateUserName(string $source): string
    {
        switch ($source) {
            case 'wechat_mp':
            case 'wechat_scan':
            case 'wechat_mini_program':
                $source = 'wechat';
                break;
        }
        do {
            $userName = $source . '_' . Random::build('alnum', 6);
        } while (Db::name('user')->where('username', $userName)->value('id'));
        return $userName;
    }

    private static function httpToHttps(string $url): string
    {
        return preg_replace('/^http(s)?:\/\//', 'https://', $url);
    }
}